﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GeneticAlgorythm.Utils;
using System.Diagnostics;
using GeneticAlgorythm.Problem;

namespace GeneticAlgorythm.Algorythm
{
    public class Population
    {
        private List<Individual> _individuals;
        private long _numberOfIterations = 0;
        private ProblemInstance _problemData;
        private int _percentageOfIndividualsInNewPopulation = 40;
        private int _populationTargetSize;
        private int _numberOfMutationsInOneChromosome = 1;
        private int _individualsLifeLength = 0;
        private Stopwatch _timer = new Stopwatch();

        public Population()
        {
            _individuals = new List<Individual>();
        }

        public Population(PopulationSaver populationSaver, ProblemInstance problemData, int individualsLifeLength = 0)  
        {
            _individualsLifeLength = individualsLifeLength;
            _individuals = new List<Individual>();
            populationSaver.load(this);

            foreach(Individual individual in _individuals)
            {
                individual.setDeathAge(_individualsLifeLength);
            }

            _problemData = problemData;
            _populationTargetSize = _individuals.Count;
            countObjectiveFunctionValues();
        }
        public Population(List<Individual> individuals, ProblemInstance problemData)
        {
            _individuals = individuals;
            _problemData = problemData;
            _populationTargetSize = individuals.Count;

            countObjectiveFunctionValues();
        }
        public void add(Individual individual)
        {
            _individuals.Add(individual);
        }

        public void saveToFile(PopulationSaver populationSaver)
        {
            populationSaver.save(this);
        }

        public override string ToString()
        {
            string result="";
            foreach(Individual individual in _individuals){
                result += individual.ToString();
            }
            return result;
        }

        public override bool Equals(object obj)
        {
            Population otherPopulation = (Population) obj;
            if(!getSize().Equals(otherPopulation.getSize())) 
                return false;

            for(int i=0;i<getSize();i++){
                if(getIndividual(i).Equals(otherPopulation.getIndividual(i)))
                    return true;
            }
            return false;
        }

        public int getSize()
        {
            return _individuals.Count;
        }

        public Individual getIndividual(int i){
            return _individuals.ElementAt(i);
        }
        public List<Individual> getIndividuals()
        {
            return _individuals;
        }
        public void setPercentageOfIndividualsInNewPopulation(int value){
            _percentageOfIndividualsInNewPopulation = value;
        }
        public void nexIteration(Selection selectionType, Crossover crossoverType, int percentageOfMutations)
        {
            killOldIndividuals();
            makeIndividualsOlder();
            _timer.Restart();
            countObjectiveFunctionValues();
            _timer.Stop();
           // System.Console.WriteLine("Obliczanie funkcji celu: " + _timer.ElapsedMilliseconds);
            _timer.Restart();
            selectNewPopulation(selectionType);
            _timer.Stop();
            //System.Console.WriteLine("Selekcja: " + _timer.ElapsedMilliseconds);
            _timer.Restart();
            crossover(crossoverType);
            _timer.Stop();
            //System.Console.WriteLine("Krzyzowanie: " + _timer.ElapsedMilliseconds);
            _timer.Restart();
            mutate(percentageOfMutations);
            _timer.Stop();
            //System.Console.WriteLine("Mutacja: " + _timer.ElapsedMilliseconds);
            _numberOfIterations++;
        }

        public Individual getBestIndividual()
        {
            countObjectiveFunctionValues();
            Individual bestIndividual = _individuals.ElementAt(0);
            foreach (Individual individual in _individuals)
            {
                if (individual.getObjectiveFunctionValue() < bestIndividual.getObjectiveFunctionValue())
                    bestIndividual = individual;
            }
            return bestIndividual;
        }
        private void killOldIndividuals()
        {
            _individuals.RemoveAll(individual => individual.isTooOld());
        }

        private void makeIndividualsOlder() 
        {
            foreach (Individual individual in _individuals)
            {
                individual.makeOlder();
            }
        }

        private void countObjectiveFunctionValues()
        {
            List<Chromosome> chromosomes = new List<Chromosome>();
            foreach (Individual individual in _individuals)
            {
                chromosomes.Add(individual.getChromosome());
            }
            float[] objectiveFunctionValues = _problemData.calculateObjectiveFunctionValue(chromosomes);

            for(int i = 0; i < _individuals.Count ; i++)
            {
                _individuals[i].setObjectiveFunctionValue(objectiveFunctionValues[i]);
            }
        }

        private void selectNewPopulation(Selection selectionType) {
            selectionType.addPopulation(this);
            _individuals = selectionType.select(_percentageOfIndividualsInNewPopulation);

        }

        private void crossover(Crossover crossoverType) {
            while(_populationTargetSize > _individuals.Count){

                Individual firstIndividual = _individuals[RandomGenerator.getInstance().generateRandomIntegerFromZeroTo(_individuals.Count)];
                Individual secondIndividual = _individuals[RandomGenerator.getInstance().generateRandomIntegerFromZeroTo(_individuals.Count)];

                int loopIterations = 0;
                while (firstIndividual.Equals(secondIndividual) && loopIterations <10)
                {
                    secondIndividual = _individuals[RandomGenerator.getInstance().generateRandomIntegerFromZeroTo(_individuals.Count)];
                    loopIterations++;
                }

                List<Chromosome> children = crossoverType.execute(firstIndividual.getChromosome(), secondIndividual.getChromosome());

                foreach(Chromosome chromosome in children)
                {
                    Individual childrenIndividual = new Individual(chromosome, _individualsLifeLength);
                    _individuals.Add(childrenIndividual);
                }
            }
        }

        private void mutate(int percentageOfMutations){
            int numberOfMutations = _individuals.Count * percentageOfMutations / 100;

            for (int i = 0; i < numberOfMutations; i++)
            {
                _individuals[RandomGenerator.getInstance().generateRandomIntegerFromZeroTo(_individuals.Count)]
                    .mutate(_numberOfMutationsInOneChromosome);
            }
        }
    }
}
